home *** CD-ROM | disk | FTP | other *** search
/ Shareware Grab Bag / Shareware Grab Bag.iso / 007 / tsr25src.arc / MAPMEM.PAS < prev    next >
Pascal/Delphi Source File  |  1987-06-02  |  30KB  |  906 lines

  1. {**************************************************************************
  2. *   Maps system memory blocks for MS/PCDOS 2.0 and higher.                *
  3. *   Also maps expanded memory allocation blocks                           *
  4. *   Copyright (c) 1986 Kim Kokkonen, TurboPower Software.                 *
  5. *   Released to the public domain for personal, non-commercial use only.  *
  6. ***************************************************************************
  7. *   version 1.0 1/2/86                                                    *
  8. *   version 1.1 1/10/86                                                   *
  9. *     running under DOS 2.X, where block owner names are unknown          *
  10. *   version 1.2 1/22/86                                                   *
  11. *     a bug in parsing the owner name of the block                        *
  12. *     a quirk in the way that the DOS PRINT buffer installs itself        *
  13. *     minor cosmetic changes                                              *
  14. *   version 1.3 2/6/86                                                    *
  15. *     smarter filtering for processes that deallocate their environment   *
  16. *   version 1.4 2/23/86                                                   *
  17. *     add a map of Expanded memory (EMS) as well                          *
  18. *   version 1.5 2/26/86                                                   *
  19. *     change format of last memory block                                  *
  20. *     change to more reliable scheme of finding first block               *
  21. *       (thanks to Chris Dunford for pointing out a useful                *
  22. *        undocumented DOS function).                                      *
  23. *     support environment lengths up to 32K                               *
  24. *   version 1.6 3/8/86                                                    *
  25. *     support "verbose" output mode                                       *
  26. *       display open file handles                                         *
  27. *       show command line of each block                                   *
  28. *   version 1.7 3/24/86                                                   *
  29. *     work around Turbo 3.00B bug with Delete procedure and length 255    *
  30. *     filter out command lines of programs which relocate over their      *
  31. *       command line at PSP:$80                                           *
  32. *     fix treatment of handle counts from PSP                             *
  33. *     add display of number of memory blocks per PSP to verbose mode      *
  34. *     accept V, -V, or /V for the verbose switch                          *
  35. *   version 1.8 4/20/86                                                   *
  36. *     change verbose mode to show each block individually                 *
  37. *   version 1.9 5/22/86                                                   *
  38. *     synchronize with RELEASE                                            *
  39. *   version 2.0 6/17/86                                                   *
  40. *     synchronize with RELEASE                                            *
  41. *   version 2.1 7/18/86                                                   *
  42. *     wrap long vector lists                                              *
  43. *   version 2.2 3/4/87                                                    *
  44. *     add support for WATCH files                                         *
  45. *   version 2.3 5/1/87                                                    *
  46. *     use in-memory WATCH data                                            *
  47. *     display disabled status of TSRs                                     *
  48. *   version 2.4 5/17/87                                                   *
  49. *     avoid use of EMS call $4B, which doesn't work in many EMS           *
  50. *       implementations                                                   *
  51. *   version 2.5 5/26/87                                                   *
  52. *     correct problem with MAPMEM run in batch file with WATCH            *                                    *
  53. ***************************************************************************
  54. *   telephone: 408-438-8608, CompuServe: 72457,2131.                      *
  55. *   requires Turbo version 3 to compile.                                  *
  56. *   Compile with mAx dynamic memory = FFFF.                               *
  57. ***************************************************************************}
  58.  
  59. {$P128}
  60.  
  61. program MapMem;
  62.   {-look at the system memory map using DOS memory control blocks}
  63. const
  64.   Version = '2.5';
  65.   MaxBlocks = 100;            {max number of DOS memory blocks checked}
  66.   MaxVector = $FF;            {highest interrupt vector checked for trapping}
  67.  
  68.   WatchID = 'TSR WATCHER';    {marking string for WATCH}
  69.  
  70.   {offsets into resident copy of WATCH.COM for data storage}
  71.   WatchOffset = $81;
  72.   NextChange = $104;
  73.   ChangeVectors = $220;
  74.   OrigVectors = $620;
  75.   CurrVectors = $A20;
  76.  
  77. type
  78.   Pathname = string[64];
  79.   AllStrings = string[255];
  80.  
  81.   BlockType = 0..MaxBlocks;
  82.   Block =
  83.   record                      {store info about each memory block as it is found}
  84.     idbyte : Byte;
  85.     mcb : Integer;
  86.     psp : Integer;
  87.     len : Integer;
  88.     psplen : Integer;
  89.     env : Integer;
  90.     cnt : Integer;
  91.   end;
  92.   BlockArray = array[BlockType] of Block;
  93.  
  94.   registers =
  95.   record
  96.     case Integer of
  97.       1 : (ax, bx, cx, dx, bp, si, di, ds, es, flags : Integer);
  98.       2 : (al, ah, bl, bh, cl, ch, dl, dh : Byte);
  99.   end;
  100.  
  101. var
  102.   Blocks : BlockArray;
  103.   WatchBlock, BlockNum : BlockType;
  104.   UseHook, Verbose, UseWatch : Boolean;
  105.  
  106.   procedure Abort(msg : AllStrings);
  107.     {-halt in case of error}
  108.   begin
  109.     WriteLn(msg);
  110.     Halt(1);
  111.   end {Abort} ;
  112.  
  113.   function StUpcase(s : Pathname) : Pathname;
  114.     {-return the upper case of a string}
  115.   var
  116.     i : Byte;
  117.   begin
  118.     for i := 1 to Length(s) do
  119.       s[i] := UpCase(s[i]);
  120.     StUpcase := s;
  121.   end {stupcase} ;
  122.  
  123.   procedure FindTheBlocks;
  124.     {-scan memory for the allocated memory blocks}
  125.   const
  126.     MidBlockID = $4D;         {byte DOS uses to identify part of MCB chain}
  127.     EndBlockID = $5A;         {byte DOS uses to identify last block of MCB chain}
  128.   var
  129.     mcbSeg : Integer;         {segment address of current MCB}
  130.     nextSeg : Integer;        {computed segment address for the next MCB}
  131.     gotFirst : Boolean;       {true after first MCB is found}
  132.     gotLast : Boolean;        {true after last MCB is found}
  133.     idbyte : Byte;            {byte that DOS uses to identify an MCB}
  134.  
  135.     function GetStartMCB : Integer;
  136.       {-return the first MCB segment}
  137.     var
  138.       reg : registers;
  139.     begin
  140.       reg.ah := $52;
  141.       MsDos(reg);
  142.       GetStartMCB := MemW[reg.es:(reg.bx-2)];
  143.     end {getstartmcb} ;
  144.  
  145.     procedure StoreTheBlock(var mcbSeg, nextSeg : Integer;
  146.                             var gotFirst, gotLast : Boolean);
  147.       {-store information regarding the memory block}
  148.     var
  149.       nextID : Byte;
  150.       pspAdd : Integer;       {segment address of the current PSP}
  151.       mcbLen : Integer;       {size of the current memory block in paragraphs}
  152.  
  153.     begin
  154.  
  155.       mcbLen := MemW[mcbSeg:3]; {size of the MCB in paragraphs}
  156.       nextSeg := Succ(mcbSeg+mcbLen); {where the next MCB should be}
  157.       pspAdd := MemW[mcbSeg:1]; {address of program segment prefix for MCB}
  158.       nextID := Mem[nextSeg:0];
  159.  
  160.       if gotLast or (nextID = EndBlockID) or (nextID = MidBlockID) then begin
  161.         BlockNum := Succ(BlockNum);
  162.         gotFirst := True;
  163.         with Blocks[BlockNum] do begin
  164.           idbyte := Mem[mcbSeg:0];
  165.           mcb := mcbSeg;
  166.           psp := pspAdd;
  167.           env := MemW[pspAdd:$2C];
  168.           len := mcbLen;
  169.           psplen := 0;
  170.           cnt := 1;
  171.         end;
  172.       end;
  173.  
  174.     end {storetheblock} ;
  175.  
  176.   begin
  177.  
  178.     {initialize}
  179.     mcbSeg := GetStartMCB;
  180.     gotFirst := False;
  181.     gotLast := False;
  182.     BlockNum := 0;
  183.  
  184.     {scan all memory until the last block is found}
  185.     repeat
  186.       idbyte := Mem[mcbSeg:0];
  187.       if idbyte = MidBlockID then begin
  188.         StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  189.         if gotFirst then mcbSeg := nextSeg else mcbSeg := Succ(mcbSeg);
  190.       end else if gotFirst and (idbyte = EndBlockID) then begin
  191.         gotLast := True;
  192.         StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  193.       end else
  194.         {start block was invalid}
  195.         Abort('corrupted allocation chain or program error');
  196.     until gotLast;
  197.  
  198.   end {findtheblocks} ;
  199.  
  200.   function FindMark(markName : AllStrings; markOffset : Integer) : Integer;
  201.     {-find the last memory block matching idstring at offset idoffset}
  202.   var
  203.     b : BlockType;
  204.     MemMark : Boolean;
  205.  
  206.     function HasIDstring(segment : Integer;
  207.                          idString : AllStrings;
  208.                          idOffset : Integer) : Boolean;
  209.       {-return true if idstring is found at segment:idoffset}
  210.     var
  211.       len : Byte absolute idString;
  212.       tString : AllStrings;
  213.       tlen : Byte absolute tString;
  214.  
  215.     begin
  216.       tlen := len;
  217.       Move(Mem[segment:idOffset], tString[1], len);
  218.       HasIDstring := (tString = idString);
  219.     end {HasIDstring} ;
  220.  
  221.   begin
  222.     {scan from the last block down to find the last MARK TSR}
  223.     b := BlockNum;
  224.     MemMark := False;
  225.     repeat
  226.       if Blocks[b].psp = CSeg then
  227.         {assure this program's command line is not matched}
  228.         b := Pred(b)
  229.       else if HasIDstring(Blocks[b].psp, markName, markOffset) then
  230.         {Mark found}
  231.         MemMark := True
  232.       else
  233.         {Keep looking}
  234.         b := Pred(b);
  235.     until (b < 1) or MemMark;
  236.  
  237.     UseWatch := MemMark;
  238.     FindMark := b;
  239.  
  240.   end {findmark} ;
  241.  
  242.   function Cardinal(i : Integer) : Real;
  243.     {-return an unsigned integer 0..65535}
  244.   begin
  245.     if i < 0 then
  246.       Cardinal := 65536.0+i
  247.     else
  248.       Cardinal := i;
  249.   end {cardinal} ;
  250.  
  251.   procedure StripNonAscii(var t : Pathname);
  252.     {-return an empty string if t contains any non-printable characters}
  253.   var
  254.     ipos : Byte;
  255.     goodname : Boolean;
  256.   begin
  257.     goodname := True;
  258.     for ipos := 1 to Length(t) do
  259.       if (t[ipos] <> #0) and ((t[ipos] < ' ') or (t[ipos] > '}')) then
  260.         goodname := False;
  261.     if not(goodname) then t := '';
  262.   end {stripnonascii} ;
  263.  
  264.   procedure ShowTheBlocks;
  265.     {-analyze and display the blocks found}
  266.   const
  267.     hookst : string[14] = 'hooked vectors';
  268.     chainst : string[15] = 'chained vectors';
  269.   type
  270.     HexString = string[4];
  271.     Address = record
  272.                 offset, segment : Integer;
  273.               end;
  274.     VectorType = 0..MaxVector;
  275.   var
  276.     st, cline : Pathname;
  277.     b : BlockType;
  278.     StLen, DOSv : Byte;
  279.     CommandPSP, WatchPSP : Integer;
  280.     Vectors : array[VectorType] of Address absolute 0 : 0;
  281.     Vtable : array[VectorType] of Real;
  282.     SumNum : BlockType;
  283.     Sum : BlockArray;
  284.  
  285.     function HexB(b : Byte) : HexString;
  286.       {-return hex representation of byte}
  287.     const
  288.       hc : array[0..15] of Char = '0123456789ABCDEF';
  289.     begin
  290.       HexB := hc[b shr 4]+hc[b and $F];
  291.     end {HexB} ;
  292.  
  293.     function Hex(i : Integer) : HexString;
  294.       {-return hex representation of integer}
  295.     begin
  296.       Hex := HexB(Hi(i))+HexB(Lo(i));
  297.     end {hex} ;
  298.  
  299.     function DOSversion : Byte;
  300.       {-return the major version number of DOS}
  301.     var
  302.       reg : registers;
  303.     begin
  304.       reg.ah := $30;
  305.       MsDos(reg);
  306.       DOSversion := reg.al;
  307.     end {dosversion} ;
  308.  
  309.     function Owner(startadd : Integer) : Pathname;
  310.       {-return the name of the owner program of an MCB}
  311.     type
  312.       chararray = array[0..32767] of Char;
  313.     var
  314.       e : ^chararray;
  315.       i : Integer;
  316.       t : Pathname;
  317.  
  318.       function LongPos(m : Pathname; var s : chararray) : Integer;
  319.         {-return the position number of m in s, or 0 if not found}
  320.       var
  321.         mc : Char;
  322.         ss : Pathname;
  323.         i, maxindex : Integer;
  324.         found : Boolean;
  325.       begin
  326.         i := 0;
  327.         maxindex := SizeOf(s)-Length(m);
  328.         ss[0] := m[0];
  329.         if Length(m) > 0 then begin
  330.           mc := m[1];
  331.           repeat
  332.             while (s[i] <> mc) and (i <= maxindex) do
  333.               i := Succ(i);
  334.             if s[i] = mc then begin
  335.               Move(s[i], ss[1], Length(m));
  336.               found := (ss = m);
  337.               if not(found) then i := Succ(i);
  338.             end;
  339.           until found or (i > maxindex);
  340.           if not(found) then i := 0;
  341.         end;
  342.         LongPos := i;
  343.       end {longpos} ;
  344.  
  345.       procedure StripPathname(var pname : Pathname);
  346.         {-remove leading drive or path name from the input}
  347.       var
  348.         spos, cpos, rpos : Byte;
  349.       begin
  350.         spos := Pos('\', pname);
  351.         cpos := Pos(':', pname);
  352.         if spos+cpos = 0 then Exit;
  353.         if spos <> 0 then begin
  354.           {find the last slash in the pathname}
  355.           rpos := Length(pname);
  356.           while (rpos > 0) and (pname[rpos] <> '\') do rpos := Pred(rpos);
  357.         end else
  358.           rpos := cpos;
  359.         Delete(pname, 1, rpos);
  360.       end {strippathname} ;
  361.  
  362.       procedure StripExtension(var pname : Pathname);
  363.         {-remove the file extension}
  364.       var
  365.         dotpos : Byte;
  366.       begin
  367.         dotpos := Pos('.', pname);
  368.         if dotpos <> 0 then
  369.           Delete(pname, dotpos, 64); {<255 needed for Turbo version 3.00B bug}
  370.       end {stripextension} ;
  371.  
  372.     begin
  373.       {point to the environment string}
  374.       e := Ptr(startadd, 0);
  375.  
  376.       {find end of the standard environment}
  377.       i := LongPos(#0#0, e^);
  378.       if i = 0 then begin
  379.         {something's wrong, exit gracefully}
  380.         Owner := '';
  381.         Exit;
  382.       end;
  383.  
  384.       {end of environment found, get the program name that follows it}
  385.       t := '';
  386.       i := i+4;               {skip over #0#0#args}
  387.       repeat
  388.         t := t+e^[i];
  389.         i := Succ(i);
  390.       until (Length(t) > 64) or (e^[i] = #0);
  391.  
  392.       StripNonAscii(t);
  393.       if t = '' then
  394.         Owner := 'N/A'
  395.       else begin
  396.         StripPathname(t);
  397.         StripExtension(t);
  398.         if t = '' then t := 'N/A';
  399.         Owner := StUpcase(t);
  400.       end;
  401.  
  402.     end {owner} ;
  403.  
  404.     procedure InitVectorTable;
  405.       {-build real equivalent of vector addresses}
  406.     var
  407.       v : VectorType;
  408.  
  409.       function RealAdd(a : Address) : Real;
  410.         {-return the real equivalent of an address (pointer)}
  411.       begin
  412.         with a do
  413.           RealAdd := 16.0*Cardinal(segment)+Cardinal(offset);
  414.       end {realadd} ;
  415.  
  416.     begin
  417.       for v := 0 to MaxVector do
  418.         Vtable[v] := RealAdd(Vectors[v]);
  419.     end {initvectortable} ;
  420.  
  421.     procedure WriteVecs(start, stop, startcol, wrapcol : Integer);
  422.       {-Show either trapped or chained interrupt vectors}
  423.  
  424.       procedure WriteHooks(start, stop, startcol, wrapcol : Integer);
  425.         {-show the trapped interrupt vectors}
  426.       var
  427.         v : VectorType;
  428.         sadd, eadd : Real;
  429.         col : Integer;
  430.       begin
  431.         sadd := 16.0*Cardinal(start);
  432.         eadd := 16.0*Cardinal(stop);
  433.         col := startcol;
  434.         for v := 0 to MaxVector do
  435.           if (Vtable[v] >= sadd) and (Vtable[v] <= eadd) then begin
  436.             if col+3 > wrapcol then begin
  437.               {wrap to next line}
  438.               WriteLn;
  439.               Write('':Pred(startcol));
  440.               col := startcol;
  441.             end;
  442.             Write(HexB(v), ' ');
  443.             col := col+3;
  444.           end;
  445.       end {writehooks} ;
  446.  
  447.       procedure WriteChained(pspA, startcol, wrapcol : Integer);
  448.         {-Write Chained interrupts as determined from watch data}
  449.       type
  450.         ChangeBlock =
  451.         record                {Store info about each vector takeover}
  452.           VecNum : Byte;
  453.           case ID : Byte of
  454.             0, 1 : (VecOfs, VecSeg : Integer);
  455.             2 : (SaveCode : array[1..6] of Byte);
  456.             $FF : (pspAdd : Integer);
  457.         end;
  458.         {
  459.         ID is interpreted as follows:
  460.         00 = ChangeBlock holds the new pointer for vector vecnum
  461.         01 = ChangeBlock holds pointer for vecnum but the block is disabled
  462.         02 = ChangeBlock holds the code underneath the vector patch
  463.         FF = ChangeBlock holds the segment of a new PSP
  464.         }
  465.       var
  466.         p : ^ChangeBlock;
  467.         i, maxchg, col : Integer;
  468.         found : Boolean;
  469.       begin
  470.         {Initialize}
  471.         maxchg := MemW[WatchPSP:NextChange];
  472.         col := startcol;
  473.         found := False;
  474.         i := 0;
  475.  
  476.         while i < maxchg do begin
  477.           p := Ptr(WatchPSP, ChangeVectors+i);
  478.           with p^ do
  479.             case ID of
  480.               $FF :           {ChangeBlock starts a new PSP}
  481.                 found := (pspA = pspAdd);
  482.               $00 :           {ChangeBlock describes an active vector takeover}
  483.                 if found then begin
  484.                   {ChangeBlock specifies a vector taken over}
  485.                   if col >= wrapcol then begin
  486.                     Write(^M^J, '':Pred(startcol));
  487.                     col := startcol;
  488.                   end;
  489.                   Write(HexB(Lo(VecNum)), ' ');
  490.                   col := col+3;
  491.                 end;
  492.               $01 :           {ChangeBlock specifies a disabled takeover}
  493.                 if found then begin
  494.                   Write('disabled');
  495.                   {Don't write this more than once}
  496.                   Exit;
  497.                 end;
  498.             end;
  499.           i := i+SizeOf(ChangeBlock);
  500.         end;
  501.       end {WriteChained} ;
  502.  
  503.     begin
  504.       if start <> stop then
  505.         if UseWatch then
  506.           WriteChained(start, startcol, wrapcol)
  507.         else
  508.           WriteHooks(start, stop, startcol, wrapcol);
  509.     end {WriteVecs} ;
  510.  
  511.     procedure SortByPSP(var Blocks : BlockArray; BlockNum : BlockType);
  512.       {-sort in order of ascending PSP}
  513.     var
  514.       i, j : BlockType;
  515.       temp : Block;
  516.     begin
  517.       for i := 1 to Pred(BlockNum) do
  518.         for j := BlockNum downto Succ(i) do
  519.           if Cardinal(Blocks[j].psp) < Cardinal(Blocks[Pred(j)].psp) then begin
  520.             temp := Blocks[j];
  521.             Blocks[j] := Blocks[Pred(j)];
  522.             Blocks[Pred(j)] := temp;
  523.           end;
  524.     end {SortByPSP} ;
  525.  
  526.     procedure SumTheBlocks(var Blocks : BlockArray;
  527.                            BlockNum : BlockType;
  528.                            var Sum : BlockArray;
  529.                            var SumNum : BlockType);
  530.       {-combine the blocks with equivalent PSPs}
  531.     var
  532.       prevPSP : Integer;
  533.       b : BlockType;
  534.     begin
  535.       SumNum := 0;
  536.       prevPSP := 0;
  537.       for b := 1 to BlockNum do begin
  538.         if Blocks[b].psp <> prevPSP then begin
  539.           SumNum := Succ(SumNum);
  540.           Sum[SumNum] := Blocks[b];
  541.           prevPSP := Blocks[b].psp;
  542.           if prevPSP = CSeg then
  543.             {don't include the environment as part of free block's length}
  544.             Sum[SumNum].len := 0;
  545.         end else
  546.           with Sum[SumNum] do begin
  547.             cnt := Succ(cnt);
  548.             len := len+Blocks[b].len;
  549.           end;
  550.         {get length of the block which owns the executable program}
  551.         {for checking vector trapping next}
  552.         if Succ(Blocks[b].mcb) = Blocks[b].psp then
  553.           Sum[SumNum].psplen := Blocks[b].len;
  554.       end;
  555.     end {sumtheblocks} ;
  556.  
  557.     procedure TransferTheBlocks(var Blocks : BlockArray;
  558.                                 BlockNum : BlockType;
  559.                                 var Sum : BlockArray;
  560.                                 var SumNum : BlockType);
  561.       {-fill in the Sum array with a little initialization}
  562.     var
  563.       b : BlockType;
  564.     begin
  565.       for b := 1 to BlockNum do begin
  566.         Sum[b] := Blocks[b];
  567.         with Sum[b] do begin
  568.           cnt := 1;
  569.           if (Succ(mcb) = psp) and (psp <> 0) then
  570.             psplen := len
  571.           else
  572.             psplen := 0;
  573.         end;
  574.       end;
  575.       SumNum := BlockNum;
  576.     end {transfertheblocks} ;
  577.  
  578.     function OpenHandles(psp : Integer) : Integer;
  579.       {-return the number of open handles owned by a process}
  580.     var
  581.       h, o : Integer;
  582.       b : Byte;
  583.     begin
  584.       h := 0;
  585.       if (psp <> 8) and (cline <> 'N/A') then
  586.         for o := 0 to 19 do begin
  587.           b := Mem[psp:$18+o];
  588.           if not(b in [$FF, 0..2]) then
  589.             h := Succ(h);
  590.         end;
  591.       OpenHandles := h;
  592.     end {openhandles} ;
  593.  
  594.     function CommandLine(psp : Integer) : Pathname;
  595.       {-return the command line of the PSP}
  596.     var
  597.       t, s : Pathname;
  598.     begin
  599.       if (psp <> 8) then begin
  600.         Move(Mem[psp:$80], t, 65);
  601.         if t[0] > #64 then t[0] := #64;
  602.         s := t;
  603.         StripNonAscii(t);
  604.         if s <> t then
  605.           {command line has been written over}
  606.           t := 'N/A'
  607.         else
  608.           {strip leading blanks}
  609.           while (Length(t) > 0) and (t[1] = #32) do Delete(t, 1, 1);
  610.       end else
  611.         {psp=8 is a special block owned by DOS containing the CONFIG.SYS drivers}
  612.         t := '';
  613.       CommandLine := t;
  614.     end {commandline} ;
  615.  
  616.     function PrevBlock(b : BlockType; psp : Integer) : BlockType;
  617.       {-return highest block with number less than b having a PSP matching psp}
  618.       {-return 0 if none}
  619.     var
  620.       t : BlockType;
  621.       found : Boolean;
  622.     begin
  623.       found := False;
  624.       t := Pred(b);
  625.       while (t > 0) and not(found) do begin
  626.         found := (Sum[t].psp = psp);
  627.         if not(found) then t := Pred(t);
  628.       end;
  629.       PrevBlock := t;
  630.     end {prevblock} ;
  631.  
  632.     procedure WriteTitle;
  633.     begin
  634.       Write('Allocated Memory Map - by TurboPower Software - Version ', Version);
  635.  
  636.       if Verbose then begin
  637.         WriteLn('  (verbose)');
  638.         WriteLn;
  639.         Write(' PSP  MCB files bytes owner    command line  ');
  640.         if UseWatch then
  641.           WriteLn(chainst)
  642.         else
  643.           WriteLn(hookst);
  644.         WriteLn('---- ---- ----- ----- -------- ------------- -----------------------------');
  645.       end else begin
  646.         WriteLn;
  647.         WriteLn;
  648.         Write(' PSP  blks bytes owner    command line        ');
  649.         if UseWatch then
  650.           WriteLn(chainst)
  651.         else
  652.           WriteLn(hookst);
  653.         WriteLn('----- ---- ----- -------- ------------------- ------------------------------');
  654.       end;
  655.     end {WriteTitle} ;
  656.  
  657.   begin
  658.  
  659.     WriteTitle;
  660.  
  661.     {Get critical PSP addresses before sorting blocks}
  662.     CommandPSP := Blocks[2].psp;
  663.     if UseWatch then
  664.       WatchPSP := Blocks[WatchBlock].psp
  665.     else
  666.       InitVectorTable;
  667.  
  668.     {Rearrange the blocks for presentation}
  669.     if Verbose then
  670.       TransferTheBlocks(Blocks, BlockNum, Sum, SumNum)
  671.     else begin
  672.       SortByPSP(Blocks, BlockNum);
  673.       SumTheBlocks(Blocks, BlockNum, Sum, SumNum);
  674.     end;
  675.  
  676.     {Get DOS version number to see whether environment has program names}
  677.     DOSv := DOSversion;
  678.  
  679.     for b := 1 to SumNum do with Sum[b] do begin
  680.  
  681.       {get the command line which invoked the program}
  682.       if b = SumNum then
  683.         cline := ''
  684.       else
  685.         cline := CommandLine(psp);
  686.  
  687.       {write out numerical information}
  688.       Write(Hex(psp), ' ');   {PSP address}
  689.       if Verbose then begin
  690.         Write(Hex(mcb), '  ', {MCB address}
  691.         OpenHandles(psp):2, '  '); {number of open file handles}
  692.       end else
  693.         Write(cnt:3, '  ');   {number of blocks}
  694.  
  695.       Write(16.0*Cardinal(len):6:0, ' '); {size of block in bytes}
  696.  
  697.       {get the program owning this block by scanning the environment}
  698.       if psp = CSeg then
  699.         st := 'free'
  700.       else if psp = CommandPSP then
  701.         st := 'command'
  702.       else if psp = Sum[1].psp then
  703.         st := 'config'
  704.       else if (DOSv >= 3) then begin
  705.         if Verbose then begin
  706.           if Succ(mcb) = env then
  707.             {this is the environment block}
  708.             st := Owner(env)
  709.           else if PrevBlock(b, psp) <> 0 then
  710.             {this is the block that goes with the environment}
  711.             st := Owner(Sum[PrevBlock(b, psp)].env)
  712.           else
  713.             st := 'N/A';
  714.         end else if cnt > 1 then
  715.           st := Owner(env)
  716.         else
  717.           st := 'N/A';
  718.       end else
  719.         st := 'N/A';
  720.       while Length(st) < 9 do
  721.         st := st+' ';
  722.       Write(st);
  723.  
  724.       {write the command line that invoked the program}
  725.       if Verbose then
  726.         StLen := 13
  727.       else
  728.         StLen := 19;
  729.       if Length(cline) > StLen-3 then
  730.         cline := Copy(cline, 1, StLen-3)+'...'
  731.       else
  732.         while Length(cline) < StLen do cline := cline+' ';
  733.       Write(cline, ' ');
  734.  
  735.       {write the trapped interrupt vectors}
  736.       if Verbose then
  737.         WriteVecs(psp, psp+psplen, 46, 75)
  738.       else if (b <> SumNum) then
  739.         WriteVecs(psp, psp+psplen, 47, 75);
  740.  
  741.       WriteLn;
  742.     end;
  743.  
  744.   end {showtheblocks} ;
  745.  
  746.   procedure ShowTheEMSblocks;
  747.     {-map out expanded memory, if present}
  748.   const
  749.     EMSinterrupt = $67;       {the vector used by the expanded memory manager}
  750.     MaxHandles = 255;
  751.  
  752.   type
  753.     HandlePageRecord =
  754.     record
  755.       handle : Integer;
  756.       numpages : Integer;
  757.     end;
  758.  
  759.     PageArray = array[0..MaxHandles] of HandlePageRecord;
  760.     PageArrayPtr = ^PageArray;
  761.     Pathname = string[64];
  762.  
  763.   var
  764.     EMSregs : registers;
  765.     EMShandles : Integer;
  766.     Map : PageArrayPtr;
  767.     TotalPages : Integer;
  768.  
  769.     function EMSpresent : Boolean;
  770.       {-return true if EMS memory manager is present}
  771.     var
  772.       f : file;
  773.       present : Boolean;
  774.     begin
  775.       {"file handle" defined by the expanded memory manager at installation}
  776.       Assign(f, 'EMMXXXX0');
  777.       {$I-} Reset(f) {$I+} ;
  778.       present := (IOResult = 0);
  779.       if present then
  780.         Close(f);
  781.       EMSpresent := present;
  782.     end {EMSpresent} ;
  783.  
  784.     function EMSpagesAvailable(var TotalPages : Integer) : Integer;
  785.       {-return the number of 16K expanded memory pages available and unallocated}
  786.     begin
  787.       EMSregs.ah := $42;
  788.       Intr(EMSinterrupt, EMSregs);
  789.       if EMSregs.ah <> 0 then begin
  790.         WriteLn('EMS device not responding');
  791.         EMSpagesAvailable := 0;
  792.         Exit;
  793.       end;
  794.       EMSpagesAvailable := EMSregs.bx;
  795.       TotalPages := EMSregs.dx;
  796.     end {EMSpagesAvailable} ;
  797.  
  798.     procedure EMSpageMap(var PageMap : PageArray; var EMShandles : Integer);
  799.       {-return an array of the allocated memory blocks}
  800.     begin
  801.       EMSregs.ah := $4D;
  802.       EMSregs.es := Seg(PageMap);
  803.       EMSregs.di := Ofs(PageMap);
  804.       EMSregs.bx := 0;
  805.       Intr(EMSinterrupt, EMSregs);
  806.       if EMSregs.ah <> 0 then begin
  807.         WriteLn('EMS device not responding');
  808.         EMShandles := 0;
  809.       end else
  810.         EMShandles := EMSregs.bx;
  811.     end {EMSpageMap} ;
  812.  
  813.     procedure WriteEMSmap(PageMap : PageArray; handles : Integer);
  814.       {-write out the EMS page map}
  815.     var
  816.       h : Integer;
  817.     begin
  818.       WriteLn('block   bytes   (Expanded Memory)');
  819.       WriteLn('-----   ------');
  820.       for h := 0 to Pred(handles) do
  821.         WriteLn(h:5, '  ', (16384.0*Cardinal(PageMap[h].numpages)):7:0);
  822.     end {writeEMSmap} ;
  823.  
  824.   begin
  825.     if not(EMSpresent) then
  826.       Exit;
  827.     WriteLn;
  828.     {Get space for the largest possible page map}
  829.     GetMem(Map, 2048);
  830.     EMSpageMap(Map^, EMShandles);
  831.     WriteEMSmap(Map^, EMShandles);
  832.     WriteLn(' free  ', (16384.0*Cardinal(EMSpagesAvailable(TotalPages))):7:0);
  833.     WriteLn('total  ', (16384.0*Cardinal(TotalPages)):7:0);
  834.   end {showtheemsblocks} ;
  835.  
  836.   procedure GetOptions;
  837.     {-Analyze command line for options}
  838.   const
  839.     unknop : string[24] = 'Unknown command option: ';
  840.   var
  841.     arg : AllStrings;
  842.     arglen : Byte absolute arg;
  843.     i : Integer;
  844.  
  845.     procedure WriteHelp;
  846.       {-Show the options}
  847.     begin
  848.       WriteLn('MAPMEM ', Version, ', by TurboPower Software');
  849.       WriteLn('====================================================');
  850.       WriteLn;
  851.       WriteLn('MAPMEM produces a report showing what memory resident');
  852.       WriteLn('programs are installed, how much memory each uses, and');
  853.       WriteLn('what interrupt vectors are taken over.');
  854.       WriteLn;
  855.       WriteLn('MAPMEM accepts the following command line syntax:');
  856.       WriteLn;
  857.       WriteLn('  MAPMEM [Options]');
  858.       WriteLn;
  859.       WriteLn('Options may be preceded by either / or -. Valid options');
  860.       WriteLn('are as follows:');
  861.       WriteLn('     /V     Verbose report.');
  862.       WriteLn('     /?     Write this help screen.');
  863.       Halt(1);
  864.     end {WriteHelp} ;
  865.  
  866.   begin
  867.  
  868.     WriteLn;
  869.     {Initialize defaults}
  870.     Verbose := False;
  871.     UseHook := False;
  872.  
  873.     i := 1;
  874.     while i <= ParamCount do begin
  875.       arg := ParamStr(i);
  876.       if (arg[1] = '?') then
  877.         WriteHelp
  878.       else if (arg[1] = '-') or (arg[1] = '/') then
  879.         case arglen of
  880.           1 : Abort('Missing command option following '+arg);
  881.           2 : case UpCase(arg[2]) of
  882.                 '?' : WriteHelp;
  883.                 'H' : UseHook := True;
  884.                 'V' : Verbose := True;
  885.               else
  886.                 Abort(unknop+arg);
  887.               end;
  888.         else
  889.           Abort(unknop+arg);
  890.         end
  891.       else
  892.         Abort(unknop+arg);
  893.       i := Succ(i);
  894.     end;
  895.  
  896.   end {GetOptions} ;
  897.  
  898. begin                         {MapMem}
  899.   GetOptions;
  900.   FindTheBlocks;
  901.   WatchBlock := FindMark(WatchID, WatchOffset);
  902.   UseWatch := UseWatch and not(UseHook);
  903.   ShowTheBlocks;
  904.   ShowTheEMSblocks;
  905. end.                          {MapMem}
  906.